/*
 * Copyright 2009 Zero Separation
 *
 *     This file is part of PDSUtilities.
 *
 *  PDSUtilities is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  PDSUtilities is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with PDSUtilities.  If not, see <http://www.gnu.org/licenses/>.
 *
 */


package com.zero_separation.pds.utilities.indexingmaps;

import com.sun.sgs.app.AppContext;
import com.sun.sgs.app.ManagedReference;
import java.math.BigInteger;

/**
 * This class is used to wrap any reference in a class which needs to be accessed
 * by a ScalableIndexingMap. The get() and set() methods should be used to
 * modify and access the contained value. Once an IndexableReference is placed in
 * an object and the object indexed then that IndexableReference object MUST not be
 * discarded as that will break any attached indexes.
 * 
 * Recommended practice is to make the Indexable itself final. This includes
 * when entering null values in the Indexable.
 *
 * <p>For example:
<code><pre>
private final IndexableReference<X> ref = new IndexableReference(null);
</pre></code>
 *
 * Instead of:
<code><pre>
    void setX(X x) {
        ref = new IndexableReference<X>(x);
    }
</pre></code>
 * Use:
<code><pre>
    void setX(X x) {
        ref.set(x);
    }
</pre></code>
 *
 * The set function will create a managed reference as required.
 *
 * @param <T> The type of field which is being wrapped to allow indexing.
 */
public class IndexableReference<T> extends Indexable<T> {

    /** The version of the serialized form of this class. */
    private static final long serialVersionUID = 1L;

    private ManagedReference<T> fieldRef;

    /**
     * Constructs a new IndexableField with its value initialised.
     *
     * @param t The value to initialise the field too.
     */
    public IndexableReference(BigInteger parentId, T t) {
        super(parentId);
        if (t==null)
            fieldRef = null;
        else
            fieldRef = AppContext.getDataManager().createReference(t);
    }

    /**
     * Returns the managed object pointed to by this IndexableReference.
     *
     * @return The contained value.
     */
    public T get() {
        if (fieldRef == null)
            return null;
        else
            return fieldRef.get();
    }

    /**
     * Returns the managed object pointed to by this IndexableReference and marks
     * that object with the data manager as having been modified.
     *
     * @return The contained value.
     */
    public T getForUpdate() {
        if (fieldRef == null)
            return null;
        else
            return fieldRef.getForUpdate();
    }

    /**
     * Set the value of the object stored inside this IndexableReference.
     *
     * @param field The Managed Object to store in this IndexableReference
     * @throws KeyAlreadyInIndexException If a unique index in the IndexingMap already contains the new value
     */
    @Override
    public void set(T field) throws KeyAlreadyInIndexException {
        fireFieldChanged(field);
        if (field == null)
            fieldRef = null;
        else
            this.fieldRef = AppContext.getDataManager().createReference(field);
    }

    /**
     * IndexableReferences are considered equal if the reference they contain is
     * equal.
     *
     * @param obj
     * @return
     */
    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final IndexableReference<T> other = (IndexableReference<T>) obj;
        if (this.fieldRef != other.fieldRef && (this.fieldRef == null || !this.fieldRef.equals(other.fieldRef))) {
            return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 59 * hash + (this.fieldRef != null ? this.fieldRef.hashCode() : 0);
        return hash;
    }

}
